#include "vcl_stl.h"

#include "bkbase.h"
#include "h_base.h"
#include "h_xml.h"
using namespace h_base;

#include "simbat.h"
using namespace simbat;

static char *SimbatBase;
static char ErrMsg[8192 + 1];

static HString SyslogDir;
static HSyslog Syslog;
static bool SimpleNameFlag = false;
static bool WithinGroupFlag = false;
static char Splitter = ',';
static HString JobNameDelimiter = ":";

static void LoadFromCfg(HXmlTree &Cfg)
{
   SyslogDir = EnvEvaluate(Cfg.GetValue("SYSLOG_DIR", "$SIMBAT_BASE/etc"));
   Syslog.Setup(SyslogDir, Cfg.GetValueUpper("SYSLOG_LEVEL", "INFO"));
   JobNameDelimiter = Cfg.GetValue("JOB_NAME_DELIMITER", ":");
}

static bool LoadCfg(HString &ErrStr)
{
   char FileName[500 + 1];
   HXmlTree Cfg;
   HFileLockRead Lock("$SIMBAT_BASE/tmp/config.lck");

   snprintf(FileName, 500, "%s/etc/config.xml", SimbatBase);

   if ( !Cfg.LoadFromFile(FileName) )
   {
      ErrStr = Cfg.GetErrorString();
      return false;
   }

   LoadFromCfg(Cfg);
   return true;
}

enum HColumnType { ctName, ctPredecessor, ctCommand, ctDescription, ctPartition };

class HCheck : public HCheckBase
{
public :
   HCheck(const char *ASimbatBase, const HString &AJobNameDelimiter)
      : HCheckBase(ASimbatBase, AJobNameDelimiter)
   {}

   bool Check();
   void Resource();
   void List();
   void List3();
   void List(const vector<HColumnType> &TypeVec, int PredecessorWidth);
};

bool HCheck::Check()
{
// 检查孤立组
   for ( map<HString, HXmlTree*>::iterator Iter = GroupMap.begin();
         Iter != GroupMap.end(); Iter++ )
   {
      if ( Iter->first == MainGroupName )
      {
         continue;
      }
      set<HString> Path;
      bool Found = false;
      HString GroupName = Iter->first;
      for ( ; ; )
      {
         if ( GroupName == MainGroupName )
         {
            Found = true;
            break;
         }
         if ( Path.find(GroupName) != Path.end() )
         {
            break;
         }
         Path.insert(GroupName);
         if ( AgentMap.find(GroupName) == AgentMap.end() )
         {
            break;
         }
         GroupName = AgentMap[GroupName];
      }
      if ( !Found )
      {
         snprintf(ErrMsg, 8192, "Isolate group : %s", Iter->first.c_str());
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         BkPrintError("%s", ErrMsg);
         return false;
      }
   }

   job_info Start;

   Start.Name = "START";

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
   {
      Start.SuccJobs.insert(Iter->first);
   }

   Graph[Start.Name] = Start;

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      Job.SuccJobVec.insert(Job.SuccJobVec.end(), Job.SuccJobs.begin(), Job.SuccJobs.end());
      if ( Job.SuccJobVec.empty() )
      {
         Job.CompletedFlag = true;
      }
   }

   int StackP = 0;
   int CheckCount = 0;
   vector<HString> JobStack;

   JobStack.push_back("START");

   for ( ; ; )
   {
      job_info &ThisNode = Graph[JobStack[StackP]];
      for ( ; ThisNode.SuccJobIdx < ThisNode.SuccJobVec.size(); )
      {
         HString &SuccNodeId = ThisNode.SuccJobVec[ThisNode.SuccJobIdx];
         job_info &SuccNode = Graph[SuccNodeId];
         if ( SuccNode.CompletedFlag )
         {
            ThisNode.SuccJobIdx++;
         }
         else
         {
            StackP++;
            if ( StackP == JobStack.size() )
            {
               JobStack.push_back(SuccNodeId);
            }
            else
            {
               JobStack[StackP] = SuccNodeId;
            }
            CheckCount++;
            if ( CheckCount >= 1000 )
            {
               CheckCount = 0;
            }
            int i = StackP - 1;
            for ( ; i >= 0; i-- )
            {
               if ( JobStack[i] == SuccNodeId )
               {
                  HString ErrStr = "Found circuit(";
                  for ( ; i <= StackP; i++ )
                  {
                     ErrStr = ErrStr + JobStack[i] + ",\n";
                  }
                  ErrStr = ErrStr + ")";
                  Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrStr.c_str());
                  BkPrintError("%s", ErrStr.c_str());
                  return false;
               }
            }
            break;
         }
      }
      if ( ThisNode.SuccJobIdx == ThisNode.SuccJobVec.size() )
      {
         ThisNode.CompletedFlag = true;
         if ( StackP == 0 )
         {
            break;
         }
         StackP--;
      }
   }

   return true;
}

void HCheck::Resource()
{
   for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      map<HString, job_info>::iterator Iter2 = Iter;
      Iter2++;
      for ( ; Iter2 != Graph.end(); Iter2++ )
      {
         job_info &Job2 = Iter2->second;
         if ( HasLink(Job.Name, Job2.Name) || HasLink(Job2.Name, Job.Name) )
         {
            continue;
         }
         vector<HString> ResultVec;
         for ( set<HString>::iterator SetIter = Job.ResourceSetRead.begin();
               SetIter != Job.ResourceSetRead.end(); SetIter++ )
         {
            if ( Job2.ResourceSetWrite.find(*SetIter) != Job2.ResourceSetWrite.end() )
            {
               ResultVec.push_back(*SetIter + "(R-W)");
            }
         }
         for ( set<HString>::iterator SetIter = Job.ResourceSetWrite.begin();
               SetIter != Job.ResourceSetWrite.end(); SetIter++ )
         {
            if ( Job2.ResourceSetRead.find(*SetIter) != Job2.ResourceSetRead.end() )
            {
               ResultVec.push_back(*SetIter + "(W-R)");
            }
            if ( Job2.ResourceSetWrite.find(*SetIter) != Job2.ResourceSetWrite.end() )
            {
               ResultVec.push_back(*SetIter + "(W-W)");
            }
         }
         if ( ResultVec.empty() )
         {
            continue;
         }
         for ( int i = 0; i < ResultVec.size(); i++ )
         {
            if ( i == 0 )
            {
               printf("%s(%s),%s(%s),", Job.Name.c_str(), Job.Tag.c_str(),
                  Job2.Name.c_str(), Job2.Tag.c_str());
            }
            else
            {
               printf(" ");
            }
            printf("%s", ResultVec[i].c_str());
         }
         printf("\n");
      }
   }
}

void HCheck::List()
{
   for ( int i = 0; i < GroupNameVec.size(); i++ )
   {
      HString &GroupName = GroupNameVec[i];
      HXmlTree *GroupXml = GroupMap[GroupName];
      vector<HXmlTree*> JobVec;
      GroupXml->GetChildren("JOB", JobVec);
      for ( int j = 0; j < JobVec.size(); j++ )
      {
         HXmlTree *JobXml = JobVec[j];
         HString JobName = JobXml->GetPropertyUpper("NAME");
         HString JobType = JobXml->GetPropertyUpper("TYPE");
         printf("%-30s %-30s %s\n", GroupName.c_str(),
            JobName.c_str(), JobType.c_str());
      }
   }
}

void HCheck::List3()
{
   int Ranking = 0;
   int JobQty = 0;
   set<HString> OutputSet;

   for ( ; ; )
   {
      Ranking = JobQty + 1;
      set<HString> JobSet;
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
            Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( Job.InDegree > 0 || OutputSet.find(Job.Name) != OutputSet.end() )
         {
            continue;
         }
         JobSet.insert(Job.Name);
      }
      if ( JobSet.empty() )
      {
         break;
      }
      for ( set<HString>::iterator SetIter = JobSet.begin();
            SetIter != JobSet.end(); SetIter++ )
      {
         job_info &Job = Graph[*SetIter];
         OutputSet.insert(Job.Name);
         for ( set<HString>::iterator SetIter = Job.SuccJobs.begin();
               SetIter != Job.SuccJobs.end(); SetIter++ )
         {
            Graph[*SetIter].InDegree--;
         }
         if ( Job.Type != jtJob && Job.Type != jtEventWait && Job.Type != jtEventFire )
         {
            continue;
         }
         JobQty++;
         HString PreJobList;
         for ( set<HString>::iterator SetIter = Job.OriPreJobs.begin();
               SetIter != Job.OriPreJobs.end(); SetIter++ )
         {
            if ( PreJobList != "" )
            {
               PreJobList += ";";
            }
            PreJobList += *SetIter;
         }
         HString Description;
         for ( int i = 0; i < Job.Description.size(); i++ )
         {
            if ( Description != "" )
            {
               Description += " ";
            }
            Description = Job.Description[i];
         }
         printf("%d,%s,%s,%s,%s\n", Ranking, Job.Name.c_str(), Job.Tag.c_str(), PreJobList.c_str(), Description.c_str());
      }
   }
}

HString SimpleName(const HString &Name)
{
   if ( SimpleNameFlag )
   {
      const char *s = strchr(Name.c_str(), JobNameDelimiter[0]);
      if ( s == NULL )
      {
         return Name;
      }
      else
      {
         return HString(s + 1);
      }
   }
   else
   {
      return Name;
   }
}

HString JoinDescription(const vector<HString> &StrVec)
{
   HString Result;

   for ( int i = 0; i < StrVec.size(); i++ )
   {
      if ( i > 0 )
      {
         Result += "\\n";
      }
      Result += StrVec[i];
   }

   return Result;
}

void HCheck::List(const vector<HColumnType> &TypeVec, int PredecessorWidth)
{
   bool HasPredecessor = false;

   for ( int i = 0; i < TypeVec.size(); i++ )
   {
      if ( TypeVec[i] == ctPredecessor )
      {
         HasPredecessor = true;
      }
   }

   for ( int i = 0; i < GroupNameVec.size(); i++ )
   {
      HString &GroupName = GroupNameVec[i];
      HXmlTree *GroupXml = GroupMap[GroupName];
      vector<HXmlTree*> JobVec;
      GroupXml->GetChildren("JOB", JobVec);
      for ( int j = 0; j < JobVec.size(); j++ )
      {
         HXmlTree *JobXml = JobVec[j];
         HString JobName = GroupName + JobNameDelimiter + JobXml->GetPropertyUpper("NAME");
         job_info &Job = Graph[JobName];
         if ( Job.Type != jtJob && Job.Type != jtEventWait && Job.Type != jtEventFire )
         {
            continue;
         }
         vector<HString> PreJobVec;
         if ( HasPredecessor )
         {
            GetPreJob(Job.Name, Job.GroupId, PreJobVec, WithinGroupFlag);
         }
         for ( int PreJobIdx = 0; ; )
         {
            int ColumnCount = 0;
            for ( int k = 0; k < TypeVec.size(); k++ )
            {
               const HColumnType &Type = TypeVec[k];
               if ( ColumnCount > 0 )
               {
                  printf("%c", Splitter);
               }
               ColumnCount++;
               if ( PreJobIdx >= PredecessorWidth && Type != ctPredecessor )
               {
                  continue;
               }
               switch ( Type )
               {
               case ctName :
                  printf("%s", SimpleName(Job.Name).c_str());
                  break;
               case ctPredecessor :
                  for ( int n = 0; n < PredecessorWidth; n++ )
                  {
                     if ( n > 0 )
                     {
                        printf("%c", Splitter);
                     }
                     if ( PreJobIdx < PreJobVec.size() )
                     {
                        printf("%s", SimpleName(PreJobVec[PreJobIdx]).c_str());
                        PreJobIdx++;
                     }
                  }
                  break;
               case ctCommand :
                  printf("%s", Job.Command.c_str());
                  break;
               case ctDescription :
                  printf("%s", JoinDescription(Job.Description).c_str());
                  break;
               case ctPartition :
                  printf("%s", Job.PartitionFile.c_str());
                  break;
               }
            }
            printf("\n");
            if ( PreJobIdx == PreJobVec.size() )
            {
               break;
            }
         }
      }
   }
}

void PrintUsage()
{
   fprintf(stderr, "Usage : [PATH]/simbat_check [-l] [--simple_name] [--within_group]\n"
                   "                            [--splitter=Splitter] [--list=Column list] file_name\n");
   fprintf(stderr, "        [PATH]/simbat_check --resource file_name\n");
   fprintf(stderr, "Column can be : name, predecessor[-N], command, description, partition\n");
   fprintf(stderr, "Default N is 5\n");
   fprintf(stderr, "Default splitter is ','\n");
   fprintf(stderr, "Example : simbat_check --list=name,predecessor,description result.txt\n");
}

int main(int argc, char *argv[])
{
   if ( strchr(argv[0], '/') != NULL )
   {
      SetBaseByPath(argv[0]);
   }

   char *FileName;

   int i;
   bool ResourceFlag = false;
   bool ListFlag = false;
   bool List2Flag = false;
   bool List3Flag = false;
   vector<HColumnType> TypeVec;
   int PredecessorWidth = 5;

   for ( i = 1; i < argc; i++ )
   {
      if ( strcmp(argv[i], "--resource") == 0 )
      {
         ResourceFlag = true;
         continue;
      }
      if ( strcmp(argv[i], "-l") == 0 )
      {
         ListFlag = true;
         continue;
      }
      if ( strcmp(argv[i], "-l3") == 0 )
      {
         List3Flag = true;
         continue;
      }
      if ( memcmp(argv[i], "--splitter=", 11) == 0 )
      {
         Splitter = argv[i][11];
         continue;
      }
      if ( strcmp(argv[i], "--simple_name") == 0 )
      {
         SimpleNameFlag = true;
         continue;
      }
      if ( strcmp(argv[i], "--within_group") == 0 )
      {
         WithinGroupFlag = true;
         continue;
      }
      if ( memcmp(argv[i], "--list=", 7) == 0 )
      {
         List2Flag = true;
         vector<HString> StrVec;
         SplitString(argv[i] + 7, ',', StrVec, true);
         for ( int j = 0; j < StrVec.size(); j++ )
         {
            HString &Str = StrVec[j];
            if ( Str == "name" )
            {
               TypeVec.push_back(ctName);
               continue;
            }
            if ( Str == "command" )
            {
               TypeVec.push_back(ctCommand);
               continue;
            }
            if ( Str == "description" )
            {
               TypeVec.push_back(ctDescription);
               continue;
            }
            if ( Str == "partition" )
            {
               TypeVec.push_back(ctPartition);
               continue;
            }
            if ( Str == "predecessor" )
            {
               TypeVec.push_back(ctPredecessor);
               continue;
            }
            if ( memcmp(Str.c_str(), "predecessor-", 12) == 0 )
            {
               TypeVec.push_back(ctPredecessor);
               PredecessorWidth = StrToInt(Str.c_str() + 12);
               if ( PredecessorWidth < 1 )
               {
                  PredecessorWidth = 1;
               }
               continue;
            }
            fprintf(stderr, "Invalid column '%s'\n", Str.c_str());
            return 8;
         }
         continue;
      }
      break;
   }

   if ( i + 1 != argc )
   {
      PrintUsage();
      return 8;
   }

   FileName = argv[i];
   SimbatBase = getenv("SIMBAT_BASE");

   if ( SimbatBase == NULL )
   {
      fprintf(stderr, "$SIMBAT_BASE not defined\n");
      return 8;
   }

   HString ErrStr;

   if ( !LoadCfg(ErrStr) )
   {
      fprintf(stderr, "Load config.xml fail, %s\n", ErrStr.c_str());
      return 8;
   }


   HCheck Check(SimbatBase, JobNameDelimiter);
   char ErrMsg[8192];

   if ( !Check.Load(FileName, ErrMsg) )
   {
      fprintf(stderr, "Load %s fail, %s\n", FileName, ErrMsg);
      return 8;
   }

   if ( ResourceFlag )
   {
      Check.Resource();
      return 0;
   }

   if ( ListFlag )
   {
      Check.List();
      return 0;
   }

   if ( List3Flag )
   {
      Check.List3();
      return 0;
   }

   if ( List2Flag )
   {
      Check.List(TypeVec, PredecessorWidth);
      return 0;
   }

   if ( Check.Check() )
   {
      return 0;
   }
   else
   {
      return 8;
   }
}
